package org.rufolfheszele.andpixelbyandpixel.configuration;

import java.io.File;
import java.io.IOException;
import java.util.TreeMap;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.rufolfheszele.andpixelbyandpixel.MainApplication;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

// TODO: By storing the xml files in the asset directory, there is no chance to modify them.
// There is a private directory for every app, so we need to use it, and at the first launch
// the app must copy the files to there

/**
 * This class holds every configuration related data. It is a singleton class.
 *
 * @author Rudolf Heszele heszele@gmail.com
 * @version 1.0
 * @since 2012-03-08
 */

public class Configuration
{
	/**
	 * This method updates the value of the XML node
	 *
	 * The method searches for the correct node with the name and properties under a certain XML node
	 *
	 * @param parent The XML node which contains the node to update. It is the parent node.
	 * @param nodeName The name of the node to update
	 * @param nodeContent The new content of the node
	 * @param properties List of properties in key-value pairs of the node to update
	 * @return true if the node found an updated, false otherwise
	 */
	public static boolean updateNodeContent(Node parent, String nodeName, String nodeContent, TreeMap<String, String> properties)
	{
		NodeList children = parent.getChildNodes();
		boolean returnValue = false;

		for(int i = 0; i < children.getLength(); ++i)
		{
			Node child = children.item(i);
			
			if(child.getNodeName() == nodeName)
			{
				boolean isPropertiesOK = false;
				NamedNodeMap attributes = child.getAttributes();
				
				if(null == properties)
					isPropertiesOK = true;
				for(int j = 0; j < attributes.getLength() && false == isPropertiesOK; j++)
				{
					Attr attribute = (Attr)attributes.item(j);
					
					if(properties.get(attribute.getName()).equals(attribute.getValue()))
						isPropertiesOK = true;
				}
				if(true == isPropertiesOK)
				{
					child.getChildNodes().item(0).setNodeValue(nodeContent);
					returnValue = true;
					break;
				}
			}
		}

		return returnValue;
	}
	
	/**
	 * Default constructor of the class. Reads and parses the configuration files
	 */
	public Configuration()
	{
		DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
		DocumentBuilder db = null;
		NodeList children = null;

		try
		{
			db = dbf.newDocumentBuilder();
			
			mXmlConfig = db.parse(MainApplication.getMainApplication().getMainActivity().getResources().getAssets().open("config.xml"));
		}
		catch(ParserConfigurationException exception)
		{
			mXmlConfig = null;
		}
		catch(SAXException exception)
		{
			mXmlConfig = null;
		}
		catch(IOException exception)
		{
			mXmlConfig = null;
		}
		if(null == mXmlConfig || false == mXmlConfig.getDocumentElement().getTagName().equals("pixel-by-pixel"))
		{
			mXmlConfig = null;
			
			return;
		}
		children = mXmlConfig.getDocumentElement().getChildNodes();
		for(int i = 0; i < children.getLength(); ++i)
		{
			Node node = children.item(i);
			
			if(true == node.getNodeName().equals("network"))
			{
				mNetworkConfiguration = new NetworkConfiguration(node);
			}
			if(true == node.getNodeName().equals("levels"))
			{
				mLevels = new Levels(node);
			}
			if(true == node.getNodeName().equals("logo"))
				mLogoPath = node.getChildNodes().item(0).getNodeValue();
			if(true == node.getNodeName().equals("dot-size"))
				mDotSize = Integer.parseInt(node.getChildNodes().item(0).getNodeValue());
		}
		mCachedDotSize = mDotSize;
	}
	
	public boolean write()
    {
		boolean returnValue = true;
		
        try
        {
            // Prepare the DOM document for writing
            Source source = new DOMSource(mXmlConfig);
            // Prepare the output file
            File file = new File("config.xml");
            Result result = new StreamResult(file);
            // Write the DOM document to the file
            Transformer xformer = TransformerFactory.newInstance().newTransformer();
            
            xformer.transform(source, result);
            returnValue = mLevels.write();
        }
        catch (TransformerConfigurationException exception)
        {
        	returnValue = false;
        }
        catch (TransformerException exception)
        {
        	returnValue = false;
        }
        
        return returnValue;
    }
	
	public String getLogoPath()
	{
		return mLogoPath;
	}
	
	public void setLogoPath(String logoPath)
	{
		String nodeContent;

		mLogoPath = logoPath;
		nodeContent = logoPath;
		Configuration.updateNodeContent(mXmlConfig.getDocumentElement(), "logo", nodeContent, null);
	}
	
	public int getDotSize()
	{
		return mCachedDotSize;
	}
	
	public void setDotSize(int dotSize)
	{
		String nodeContent;

		mDotSize = dotSize;
		mCachedDotSize = dotSize;
		nodeContent = Integer.toString(dotSize);
		Configuration.updateNodeContent(mXmlConfig.getDocumentElement(), "dot-size", nodeContent, null);
	}
	
	/**
	 * Cashes the given dot size, but will not be written to the xml file
	 *
	 * @param dotSize The new dot size to be cached
	 */
	public void cacheDotSize(int dotSize)
	{
		mCachedDotSize = dotSize;
	}
	
	/**
	 * Clears the cached dot size
	 */
	public void resetCache(boolean deepReset)
	{
		mCachedDotSize = mDotSize;
		if(false == deepReset)
			return;
		if(null != mNetworkConfiguration)
			mNetworkConfiguration.resetCache();
		if(null != mLevels)
		{
			String levelNames[] = mLevels.getLevelNames().toArray(new String[0]);

			for(int i = 0; i < levelNames.length; ++i)
			{
				mLevels.getLevel(levelNames[i]).getVehicleA().resetCache();
				mLevels.getLevel(levelNames[i]).getVehicleB().resetCache();
			}
		}
	}
	
	public NetworkConfiguration getNetworkConfiguration()
	{
		return mNetworkConfiguration;
	}
	
	public Levels getLevels()
	{
		return mLevels;
	}

	private String mLogoPath							= null;
	private int mDotSize								= 0;
	private int mCachedDotSize							= 0;
	private NetworkConfiguration mNetworkConfiguration	= null;
	private Levels mLevels								= null;
	private Document mXmlConfig							= null;
}
